using System;
using System.Collections.Generic;
using System.Text;
using EnvDTE;
using Microsoft.VisualStudio.Modeling;
using EnvDTE80;
using DslFactory.Utilities.DteCodeModel;
using System.Net.Security;
using System.ServiceModel;

namespace Dslfactory.wcfLanguage
{
 internal static class InterfaceReverseEngineering
 {
  /// <summary>
  /// Fully qualified name of the Service contract attribute
  /// </summary>
  public static readonly string serviceContractAttributeFullName = "System.ServiceModel.ServiceContractAttribute";

  /// <summary>
  /// Fully qualified name of the operation contract attribute
  /// </summary>
  public static readonly string operationContractAttributeFullName = "System.ServiceModel.OperationContractAttribute";

  /// <summary>
  /// Update the Interfaces and ServiceContract of the model from the code
  /// </summary>
  /// <param name="servicesDefinition">Root element of the model</param>
  /// <param name="knownCodeTypes">Known types</param>
  public static void UpdateInterfaces(ServicesDefinition servicesDefinition, KnownCodeTypes knownCodeTypes)
  {
   List<Interface> interfaces = new List<Interface>(servicesDefinition.Interfaces); // Important (collection might be modified)
   foreach (Interface iface in interfaces)
   {
    CodeType codeInterface = knownCodeTypes.GetFullNamedType(iface.FullName);
    if (codeInterface != null)
     MergeInterface(servicesDefinition, codeInterface);
   }
  }

  public static bool CanMerge(CodeType codeType)
  {
   if (codeType is CodeInterface)
    return (IncrementalGenerator.HasAttribute(codeType, serviceContractAttributeFullName));
   else
    return false;
  }

  internal static Interface MergeType(ServicesDefinition ServicesDefinition, CodeType codeType)
  {
   if (codeType is CodeInterface)
    return MergeInterface(ServicesDefinition, codeType);
   else
    return null;
  }


  private static Interface MergeInterface(ServicesDefinition ServicesDefinition, CodeType codeType)
  {
   Store Store = ServicesDefinition.Store;
   Interface iface = ServicesDefinition.FindInterfaceOfFullName(codeType.FullName);

   using (Transaction t = Store.TransactionManager.BeginTransaction("Merging Service contract"))
   {
    if (iface == null)
    {
     if (IncrementalGenerator.HasAttribute(codeType, serviceContractAttributeFullName))
     {
      ServiceContract serviceContract;
      iface = serviceContract = new ServiceContract(Store);
      iface.FullName = codeType.FullName;
      iface.Namespace = codeType.Namespace.FullName;
      iface.Name = codeType.Name;
      iface.IsServiceContract = true;
      ServicesDefinition.Interfaces.Add(iface);
      UpdateServiceContractProperties(codeType, serviceContract);
     }
     else
     {
      iface = new Interface(Store);
      iface.FullName = codeType.FullName;
      iface.Namespace = codeType.Namespace.FullName;
      iface.Name = codeType.Name;
      iface.IsServiceContract = false;
      ServicesDefinition.Interfaces.Add(iface);
     }
    }
    else
    {
     // Updates the interface
     if (IncrementalGenerator.HasAttribute(codeType, serviceContractAttributeFullName))
     {
      iface.IsServiceContract = true;
      // Do not use iface any longer after this instruction, it might be deleted. Must re-ask for it
      iface = ServicesDefinition.FindInterfaceOfFullName(codeType.FullName);
      UpdateServiceContractProperties(codeType, iface as ServiceContract);
     }
     else
     {
      iface.IsServiceContract = false;
      // Do not use iface any longer after this instruction, it might be deleted. Must re-ask for it
      iface = ServicesDefinition.FindInterfaceOfFullName(codeType.FullName);
     }
    }


    // In every case, we need to update the operations
    IncrementalGenerator.UpdateModelRoleFromCodeElements(iface.Methods,
                                                            IncrementalGenerator.GetMethods(codeType as CodeInterface),
                                                            delegate(Method m) { return m.Signature; },
                                                            delegate(CodeFunction2 f) { return IncrementalGenerator.GetCorrectSignature(f); },
                                                            CreateMethod,
                                                            UpdateMethod);

    if (t.HasPendingChanges)
     t.Commit();
   }
   return iface;
  }

 


  /// <summary>
  /// Updates the properties for a service contract from a code type
  /// </summary>
  /// <param name="codeType">CodeType representing a service contract</param>
  /// <param name="serviceContract">Service contract to update</param>
  public static void UpdateServiceContractProperties(CodeType codeType, ServiceContract serviceContract)
  {
   List<string> notFound = new List<string>(new string[]
    {
     "Name", "Namespace", "ConfigurationName", "ProtectionLevel", "SessionMode", "CallbackContract"
    });

   // Get information from the attribute
   CodeAttribute2 attribute = IncrementalGenerator.FindFirstAttribute(codeType, serviceContractAttributeFullName);
   if (attribute != null)
    foreach (CodeAttributeArgument argument in attribute.Arguments)
    {
     notFound.Remove(argument.Name);
     switch (argument.Name)
     {
      case "Name":
       serviceContract.ServiceContractName = IncrementalGenerator.RemoveDoubleQuotes(argument.Value);
       break;
      case "Namespace":
       serviceContract.ServiceContractNamespace = IncrementalGenerator.RemoveDoubleQuotes(argument.Value);
       break;
      case "ConfigurationName":
       serviceContract.ConfigurationName = IncrementalGenerator.RemoveDoubleQuotes(argument.Value);
       break;
      case "ProtectionLevel":
       try
       {
        serviceContract.ProtectionLevel = (ProtectionLevel)(Enum.Parse(typeof(ProtectionLevel), argument.Value.Replace("ProtectionLevel.", "")));
       }
       catch (Exception)
       {
       }
       break;
      case "SessionMode":
       try
       {
        serviceContract.SessionMode = (SessionMode)(Enum.Parse(typeof(SessionMode), argument.Value.Replace("SessionMode.", "")));
       }
       catch (Exception)
       {
       }
       break;

      case "CallbackContract":
       serviceContract.CallbackContract = serviceContract.ServicesDefinition.FindServiceContractOfFullName(argument.Value.Replace("typeof(", "").Replace(")", "").Trim());
       break;
     }
    }

   // Default value for arguments that are not found
   foreach (string argumentName in notFound)
   {
    switch (argumentName)
    {
     case "Name":
      serviceContract.ServiceContractName = string.Empty;
      break;
     case "Namespace":
      serviceContract.ServiceContractNamespace = string.Empty;
      break;
     case "ConfigurationName":
      serviceContract.ConfigurationName = string.Empty;
      break;
     case "ProtectionLevel":
      serviceContract.ProtectionLevel = ProtectionLevel.None;
      break;
     case "SessionMode":
      serviceContract.SessionMode = SessionMode.Allowed;
      break;

     case "CallbackContract":
      serviceContract.CallbackContract = null;
      break;
    }
   }
  }


  /// <summary>
  /// Create a method corresponding to a CodeFunction
  /// </summary>
  private static Method CreateMethod(Store store, CodeFunction2 f)
  {
   KnownCodeTypes knownCodeTypes = KnownCodeTypes.FromStore(store);
   if (knownCodeTypes != null)
   {
    string name;
    string returnType;
    List<KeyValuePair<string, string>> parameters;

    string label = f.get_Prototype((int)(vsCMPrototype.vsCMPrototypeType | vsCMPrototype.vsCMPrototypeParamTypes | vsCMPrototype.vsCMPrototypeParamNames));
    MethodLabel.ParseLabel(knownCodeTypes, label.Replace(" (", "("), out name, out returnType, out parameters);
    label = MethodLabel.ComputeLabel(knownCodeTypes, name, returnType, parameters);
    string signature = MethodLabel.ComputeSignature(name, returnType, parameters);
    string prototype = MethodLabel.ComputePrototype(name, returnType, parameters);


    if (IncrementalGenerator.HasAttribute(f, operationContractAttributeFullName))
     return new OperationContract(store,
                                  new PropertyAssignment(Method.SignatureDomainPropertyId, signature),
                                  new PropertyAssignment(Method.LabelDomainPropertyId, label),
                                  new PropertyAssignment(Method.NameDomainPropertyId, name),
                                  new PropertyAssignment(Method.PrototypeDomainPropertyId, prototype)
                                  );
    else
     return new Method(store,
                                  new PropertyAssignment(Method.SignatureDomainPropertyId, signature),
                                  new PropertyAssignment(Method.LabelDomainPropertyId, label),
                                  new PropertyAssignment(Method.NameDomainPropertyId, name),
                                  new PropertyAssignment(Method.PrototypeDomainPropertyId, prototype)
                       );
   }
   else
    if (IncrementalGenerator.HasAttribute(f, operationContractAttributeFullName))
     return new OperationContract(store
                                  );
    else
     return new Method(store
                       );

  }

  /// <summary>
  /// Updates the method from information in the CodeFunction.
  /// </summary>
  private static void UpdateMethod(Method m, CodeFunction2 f)
  {
   KnownCodeTypes knownCodeTypes = KnownCodeTypes.FromStore(m.Store);

   if (knownCodeTypes != null)
   {
    string name;
    string returnType;
    List<KeyValuePair<string, string>> parameters;

    string label = f.get_Prototype((int)(vsCMPrototype.vsCMPrototypeType | vsCMPrototype.vsCMPrototypeParamTypes | vsCMPrototype.vsCMPrototypeParamNames));
    MethodLabel.ParseLabel(knownCodeTypes, label.Replace(" (", "("), out name, out returnType, out parameters);
    m.Label = MethodLabel.ComputeLabel(knownCodeTypes, name, returnType, parameters);
   }
   else
   {
    m.Signature = f.get_Prototype((int)(vsCMPrototype.vsCMPrototypeParamTypes | vsCMPrototype.vsCMPrototypeType)) ;
    m.Prototype = f.get_Prototype((int)(vsCMPrototype.vsCMPrototypeType | vsCMPrototype.vsCMPrototypeParamTypes | vsCMPrototype.vsCMPrototypeParamNames)).Replace(" (", "(");
   }

   if (IncrementalGenerator.HasAttribute(f, operationContractAttributeFullName))
   {
    Interface iface = m.Interface;
    string signature = m.Signature;
    m.IsOperationContract = true;
    // Warning : do not access m after this line
    OperationContract operationContract = iface.FindMethod(signature) as OperationContract;
    if (operationContract != null)
     UpdateOperationContractFromCode(operationContract, f);
   }
   else
    m.IsOperationContract = false;
  }

  /// <summary>
  /// Update the operation contract properties from the code
  /// </summary>
  /// <param name="m"></param>
  /// <param name="f"></param>
  public static void UpdateOperationContractFromCode(OperationContract operationContract, CodeFunction2 f)
  {
   CodeAttribute2 attribute = IncrementalGenerator.FindFirstAttribute(f, operationContractAttributeFullName);
   if (attribute != null)
   {
    List<string> notFound = new List<string>(new string[]
     {
      "Action", "AsyncPattern", "IsOneWay", "IsInitiating", "IsTerminating", "Name", "ProtectionLevel", "ReplyAction"
     });
    foreach (CodeAttributeArgument argument in attribute.Arguments)
    {
     notFound.Remove(argument.Name);
     switch (argument.Name)
     {
      case "Action":
       operationContract.Action = IncrementalGenerator.RemoveDoubleQuotes(argument.Value);
       break;

      case "AsyncPattern":
       try
       {
        operationContract.AsyncPattern = bool.Parse(argument.Value);
       }
       catch (Exception)
       {
       }
       break;

      case "IsOneWay":
       try
       {
        operationContract.IsOneWay = bool.Parse(argument.Value);
       }
       catch (Exception)
       {
       }
       break;

      case "IsInitiating":
       try
       {
        operationContract.IsInitiating = bool.Parse(argument.Value);
       }
       catch (Exception)
       {
       }
       break;

      case "IsTerminating":
       try
       {
        operationContract.IsTerminating = bool.Parse(argument.Value);
       }
       catch (Exception)
       {
       }
       break;

      case "Name":
       operationContract.OperationContractName = IncrementalGenerator.RemoveDoubleQuotes(argument.Value);
       break;

      case "ProtectionLevel":
       try
       {
        operationContract.ProtectionLevel = (ProtectionLevel)(Enum.Parse(typeof(ProtectionLevel), argument.Value.Replace("ProtectionLevel.", "")));
       }
       catch (Exception)
       {
       }
       break;

      case "ReplyAction":
       operationContract.ReplyAction = IncrementalGenerator.RemoveDoubleQuotes(argument.Value);
       break;
     }
    }

    // All the argument that where not found must be reset to their default value
    foreach (string argumentName in notFound)
     switch (argumentName)
     {
      case "Action":
       operationContract.Action = string.Empty;
       break;

      case "AsyncPattern":
       operationContract.AsyncPattern = false;
       break;

      case "IsOneWay":
       operationContract.IsOneWay = false;
       break;

      case "IsInitiating":
       operationContract.IsInitiating = true;
       break;

      case "IsTerminating":
       operationContract.IsTerminating = false;
       break;

      case "Name":
       operationContract.OperationContractName = string.Empty;
       break;

      case "ProtectionLevel":
       operationContract.ProtectionLevel = ProtectionLevel.None;
       break;

      case "ReplyAction":
       operationContract.ReplyAction = string.Empty;
       break;
     }
   }
  }


  public static bool IsServiceContractAttribute(CodeAttribute2 attribute)
  {
   return (attribute.FullName == serviceContractAttributeFullName) || (attribute.FullName == "System.ServiceModel.ServiceContract");
  }

  public static bool IsOperationContractAttribute(CodeAttribute2 attribute)
  {
   return (attribute.FullName == operationContractAttributeFullName) || (attribute.FullName == "System.ServiceModel.OperationContract");
  }

  public static bool CanProcessAttributeAddedOrChanged(CodeAttribute2 attribute)
  {
   return (IsServiceContractAttribute(attribute) || IsOperationContractAttribute(attribute));
  }


  public static void ProcessAttributeAddedOrChanged(ServicesDefinition servicesDefinition, CodeAttribute2 attribute)
  {
   // Service contract
   if (IsServiceContractAttribute(attribute))
    ProcessServiceContractAttributeAddedOrChanged(servicesDefinition, attribute);

    // Operation contract
   else if (IsOperationContractAttribute(attribute))
    ProcessOperationContractAttributeAddedOrChanged(servicesDefinition, attribute);
  }

  private static void ProcessOperationContractAttributeAddedOrChanged(ServicesDefinition servicesDefinition, CodeAttribute2 attribute)
  {
   CodeFunction2 codeMethod = attribute.Parent as CodeFunction2;
   if (codeMethod == null)
    return;

   CodeInterface codeInterface = codeMethod.Parent as CodeInterface;
   if (codeInterface == null)
    return;

   Interface iface = servicesDefinition.FindInterfaceOfFullName(codeInterface.FullName);
   if (iface == null)
    return;

   string prototype = codeMethod.get_Prototype((int)(vsCMPrototype.vsCMPrototypeType | vsCMPrototype.vsCMPrototypeParamTypes));
   Method method = iface.FindMethod(prototype);
   if (method == null)
    return;

   if (!(method is OperationContract))
   {
    using (Transaction transaction = servicesDefinition.Store.TransactionManager.BeginTransaction(string.Format("Transforming Method '{0}' into a Operation contract", prototype)))
    {
     method.IsOperationContract = true;
     OperationContract operationContract = iface.FindMethod(prototype) as OperationContract;
     InterfaceReverseEngineering.UpdateOperationContractFromCode(operationContract, codeMethod);
     transaction.Commit();
    }
    method = iface.FindMethod(prototype);
   }
   else
   {
    OperationContract operationContract = method as OperationContract;

    // Updates the operation contract
    using (Transaction transaction = servicesDefinition.Store.TransactionManager.BeginTransaction(string.Format("Reversing attribute 'OperationContract' on method '{0}'", prototype)))
    {
     InterfaceReverseEngineering.UpdateOperationContractFromCode(operationContract, codeMethod);
     if (transaction.HasPendingChanges)
      transaction.Commit();
    }
   }
   return;
  }

  private static void ProcessServiceContractAttributeAddedOrChanged(ServicesDefinition servicesDefinition, CodeAttribute2 attribute)
  {
   CodeInterface codeInterface = attribute.Parent as CodeInterface;
   if (codeInterface != null)
   {
    Interface iface = servicesDefinition.FindInterfaceOfFullName(codeInterface.FullName);
    if (iface == null)
     return;

    ServiceContract serviceContract = iface as ServiceContract;
    if (serviceContract == null)
    {
     using (Transaction transaction = servicesDefinition.Store.TransactionManager.BeginTransaction(string.Format("Transforming Interface '{0}' into a Service definition", codeInterface.FullName)))
     {
      iface.IsServiceContract = true;
      // Do not use iface any longer after this instruction : it might not exist any longer
      transaction.Commit();
     }
     serviceContract = servicesDefinition.FindInterfaceOfFullName(codeInterface.FullName) as ServiceContract;
    }

    if (serviceContract != null)
     using (Transaction transaction = servicesDefinition.Store.TransactionManager.BeginTransaction(string.Format("Reversing attribute '{0}' on interface '{1}'", attribute.Name, codeInterface.FullName)))
     {
      InterfaceReverseEngineering.UpdateServiceContractProperties(codeInterface as CodeType, serviceContract);
      if (transaction.HasPendingChanges)
       transaction.Commit();
     }
   }
   return;
  }

  public static void ProcessOperationContractAttributeRemoved(CodeElement parent, ServicesDefinition servicesDefinition)
  {
   CodeFunction2 codeMethod = parent as CodeFunction2;
   if (codeMethod == null)
    return;

   CodeInterface codeInterface = codeMethod.Parent as CodeInterface;
   if (codeInterface == null)
    return;

   Interface iface = servicesDefinition.FindInterfaceOfFullName(codeInterface.FullName);
   if (iface == null)
    return;

   Method method = iface.FindMethod(codeMethod.get_Prototype((int)(vsCMPrototype.vsCMPrototypeParamTypes | vsCMPrototype.vsCMPrototypeType)));
   if (method == null)
    return;


   if (method is OperationContract)
    using (Transaction transaction = servicesDefinition.Store.TransactionManager.BeginTransaction(string.Format("Transforming Service contract '{0}' into a simple interface", iface.FullName)))
    {
     method.IsOperationContract = false;
     transaction.Commit();
    }
   return;
  }

  public static bool IsOperationContractAttribute(string attributeName)
  {
   return IncrementalGenerator.AttributesEqual(attributeName, operationContractAttributeFullName);
  }

  public static bool IsServiceContractAttribute(string attributeName)
  {
   return IncrementalGenerator.AttributesEqual(attributeName, serviceContractAttributeFullName);
  }

  public static void ProcessServiceContractAttributeRemoved(CodeElement parent, ServicesDefinition servicesDefinition)
  {
   CodeInterface codeInterface = parent as CodeInterface;
   if (codeInterface != null)
   {
    ServiceContract iface = servicesDefinition.FindInterfaceOfFullName(codeInterface.FullName) as ServiceContract;
    if (iface != null)
     using (Transaction transaction = servicesDefinition.Store.TransactionManager.BeginTransaction(string.Format("Transforming Service contract '{0}' into a simple interface", iface.FullName)))
     {
      iface.IsServiceContract = false;
      // Do not use iface any longer after this instruction
      transaction.Commit();
     }
   }
  }

  internal static void ProcessMethodAdded(CodeType parent, CodeFunction2 newMethod, ServicesDefinition servicesDefinition)
  {
   CodeInterface codeInterface = parent as CodeInterface;
   if (codeInterface != null)
   {
    Interface iface = servicesDefinition.FindInterfaceOfFullName(codeInterface.FullName) as Interface;
    if (iface == null)
     return;

    Method method = iface.FindMethod(newMethod.get_Prototype((int)(vsCMPrototype.vsCMPrototypeParamTypes | vsCMPrototype.vsCMPrototypeType)));
    if (method != null)
     return;

    using (Transaction transaction = servicesDefinition.Store.TransactionManager.BeginTransaction(string.Format("Adding method", newMethod.FullName)))
    {
     // Create right method
     if (IncrementalGenerator.HasAttribute(newMethod, operationContractAttributeFullName))
      method = new OperationContract(servicesDefinition.Store);
     else
      method = new Method(servicesDefinition.Store);

     // Set properties
     method.Signature = newMethod.get_Prototype((int)(vsCMPrototype.vsCMPrototypeParamTypes | vsCMPrototype.vsCMPrototypeType));
     method.Prototype = newMethod.get_Prototype((int)(vsCMPrototype.vsCMPrototypeType | vsCMPrototype.vsCMPrototypeParamTypes | vsCMPrototype.vsCMPrototypeParamNames));
     if (method is OperationContract)
      UpdateOperationContractFromCode(method as OperationContract, newMethod);

     // Add method to parent
     method.Interface = iface;

     // Commit
     transaction.Commit();
    }
   }
  }

  internal static void ProcessMethodRemoved(CodeType type, string methodUniqueSignature, ServicesDefinition servicesDefinition)
  {
   Interface iface = servicesDefinition.FindInterfaceOfFullName(type.FullName);
   if (iface == null)
    return;
   Method method = iface.FindMethodHavingUniqueSignature(methodUniqueSignature);
   if (method != null)
    using (Transaction transaction = servicesDefinition.Store.TransactionManager.BeginTransaction(string.Format("Removing method '{0}' of interface '{1}'", methodUniqueSignature, iface.FullName)))
    {
     method.Delete();
     transaction.Commit();
    }
  }
 }
}
